package org.anderground.editor.gui.view.drawers.editor;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.anderground.core.model.Station;
import org.anderground.core.model.Transfer;
import org.anderground.core.model.Transition;
import org.anderground.core.model.Tunnel;
import org.anderground.core.util.Coord;
import org.anderground.editor.model.EditorModel;

public class StandardDrawer extends AbstractDrawer {
	private static final float SelectionRadiusTransform = 1.3f;
	private int radius;
	private Point drawerCenter = new Point(0, 0);
	private List<PolyStruct> polygons;
	private List<NameStruct> names;
	private float scale = 7f;
	private final float tunnelThikness = 7;
	private final float transferThikness = 7;
	private StandardDrawer drawer = this;

	private Font namesFont = new Font("Arial", 0, 13);

	// strokes
	private Stroke transferStroke1 = new BasicStroke(transferThikness * 1.15f);
	private Stroke transferStroke2 = new BasicStroke(transferThikness * 1f);
	private Stroke transferStroke3 = new BasicStroke(3.5f);
	private Stroke transferStroke4 = new BasicStroke(transferThikness);
	private Stroke tunnelStroke = new BasicStroke(tunnelThikness);
	private Stroke selectedTunnelStroke = new BasicStroke(tunnelThikness * 2f);

	class PolyStruct {

		public Tunnel tunnel;
		public Polygon polygon;

		public PolyStruct(Tunnel tunnel, Polygon polygon) {
			this.tunnel = tunnel;
			this.polygon = polygon;
		}
	}

	class NameStruct {
		public Station station;
		public Rectangle rect;

		public NameStruct(Station station, Graphics g) {
			this.station = station;
			String name = station.getName();
			Font font = g.getFont();

			Rectangle2D nameRect = font.getStringBounds(name, g
					.getFontMetrics().getFontRenderContext());
			int width = (int) (nameRect.getWidth());
			int height = (int) (nameRect.getHeight());

			org.anderground.core.util.Point offset = station.getLabel()
					.getOffset();
			int xPosition = drawer.getLocalPoint(station.getPosition()).x
					+ offset.getX();
			int yPosition = drawer.getLocalPoint(station.getPosition()).y
					- height + offset.getY();

			rect = new Rectangle(xPosition, yPosition, width, height);
		}
	}

	@Override
	public void draw(List<Station> stationSelection,
			List<Tunnel> tunnelSelection, Graphics g, boolean showStationNames) {
		showNames = showStationNames;
		polygons = new ArrayList<PolyStruct>();
		names = new ArrayList<NameStruct>();

		EditorModel model = EditorModel.getInstance();

		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		for (Tunnel t : model.getData().getTunnels()) {
			draw(t, g);
		}
		for (Transition t : tunnelSelection) {
			drawSelectedTransition(t, g);
		}
		for (Transfer t : model.getData().getTransfers()) {
			draw(t, g);
		}

		for (Station station : model.getData().getStations()) {
			draw(station, g);
		}

		for (Station station : stationSelection) {
			this.drawSelectedStation(station, g);
		}
	}

	public StandardDrawer() {

		// coordinateTransform = 15*scale;
		radius = (int) (scale);
	}

	public void draw(Station station, Graphics g) {
		Point center = getLocalPoint(station.getPosition());
		g.setFont(namesFont);
		g.setColor(Color.black);
		g
				.fillOval(center.x - radius, center.y - radius, radius * 2,
						radius * 2);
		if (showNames && station.getLabel().isVisible()) {
			names.add(new NameStruct(station, g));
			float t = 0.9f;
			g.setColor(new Color((int) (255 * t), (int) (255 * t),
					(int) (255 * t), 180));
			NameStruct ns = new NameStruct(station, g);
			((Graphics2D) g).fill(ns.rect);
			g.setColor(Color.black);
			((Graphics2D) g).draw(ns.rect);
			org.anderground.core.util.Point offset = station.getLabel()
					.getOffset();
			g.drawString(station.getName(), center.x + offset.getX(), center.y
					+ offset.getY());
			Point upperRight = new Point(ns.rect.x + ns.rect.width, ns.rect.y);
			Point upperLeft = ns.rect.getLocation();
			Point downRight = new Point(ns.rect.x + ns.rect.width, ns.rect.y
					+ ns.rect.height);
			Point downLeft = new Point(ns.rect.x, ns.rect.y + ns.rect.height);
			Point rectCenter = new Point(ns.rect.x + ns.rect.width / 2,
					ns.rect.y + ns.rect.height / 2);

			Point lineTo = pointWithMinDistanse(rectCenter,
					pointWithMinDistanse(pointWithMinDistanse(upperRight,
							downRight, center), pointWithMinDistanse(downLeft,
							upperLeft, center), center), center);

			((Graphics2D) g).setStroke(new BasicStroke(2));
			g.drawLine(center.x, center.y, lineTo.x, lineTo.y);
			((Graphics2D) g).setStroke(new BasicStroke());

		}
	}

	private Point pointWithMinDistanse(Point from1, Point from2, Point to) {
		if (Point.distance(from1.x, from1.y, to.x, to.y) < Point.distance(
				from2.x, from2.y, to.x, to.y)) {
			return from1;
		} else {
			return from2;
		}

	}

	public void draw(Transfer transfer, Graphics g) {
		Point from = getLocalPoint(transfer.getFromStation().getPosition());
		Point to = getLocalPoint(transfer.getToStation().getPosition());
		int radius = (int) (this.radius * 1.3f);

		saveGrpahicsParam(g);
		g.setColor(Color.BLACK);

		((Graphics2D) g).setStroke(transferStroke1);
		g.drawLine(from.x, from.y, to.x, to.y);

		((Graphics2D) g).setStroke(transferStroke2);
		g.drawOval(from.x - radius, from.y - radius, radius * 2, radius * 2);
		g
				.drawOval(to.x - radius - 1, to.y - radius - 1, radius * 2,
						radius * 2);

		((Graphics2D) g).setStroke(transferStroke3);

		g.setColor(Color.white);
		g.fillOval(from.x - radius, from.y - radius, radius * 2, radius * 2);
		g.fillOval(to.x - radius, to.y - radius, radius * 2, radius * 2);

		((Graphics2D) g).setStroke(transferStroke4);
		g.drawLine(from.x, from.y, to.x, to.y);

		g.setColor(Color.WHITE);
		restoreGraphicsParam(g);

	}

	public void draw(Tunnel tunnel, Graphics g) {
		Point from = getLocalPoint(tunnel.getFromStation().getPosition());
		Point to = getLocalPoint(tunnel.getToStation().getPosition());
		polygons.add(new PolyStruct(tunnel, getPolygon(from, to,
				tunnelThikness + 1)));
		saveGrpahicsParam(g);

		((Graphics2D) g).setStroke(tunnelStroke);
		g.setColor(new Color(tunnel.getLine().getColor()));
		g.drawLine(from.x, from.y, to.x, to.y);
		restoreGraphicsParam(g);

	}

	private Polygon getPolygon(Point start, Point end, float lineHeight) {
		Polygon pl = new Polygon();
		int dx = Math.abs(start.x - end.x);
		if (dx == 0) {
			pl.addPoint(start.x - (int) lineHeight / 2, start.y);
			pl.addPoint(start.x + (int) lineHeight / 2, start.y);
			pl.addPoint(start.x + (int) lineHeight / 2, end.y);
			pl.addPoint(start.x - (int) lineHeight / 2, end.y);
			return pl;
		}
		int dy = Math.abs(start.y - end.y);
		double cos;
		double pdy, pdx;
		double tg = dy / (double) dx;

		cos = dx / Math.sqrt(dx * dx + dy * dy);

		if ((start.x - end.x) * (start.y - end.y) > 0) {
			pdy = (cos * (lineHeight / 2d));
			pdx = (tg * pdy);
		} else {
			pdy = -(cos * (lineHeight / 2d));
			pdx = -(tg * pdy);
		}

		// now is not full tunnel(length is shorted by height/2 in both sides)
		// to solve it uncomment next block

		/*
		 * if((s.y-e.y)<0) { s = new Point(s.x-(int)pdy,(int)(s.y-pdx)); e = new
		 * Point(e.x+(int)(pdy),(int)(e.y+pdx)); } else { s = new
		 * Point(s.x+(int)pdy,(int)(s.y+pdx)); e = new
		 * Point(e.x-(int)pdy,(int)(e.y-pdx)); }
		 */

		pl.addPoint(start.x - (int) pdx, start.y + (int) pdy);
		pl.addPoint(start.x + (int) pdx, start.y - (int) pdy);
		pl.addPoint(end.x + (int) pdx, end.y - (int) pdy);
		pl.addPoint(end.x - (int) pdx, end.y + (int) pdy);
		return pl;
	}

	public Point getLocalPoint(Coord p) {
		return new Point(
				(int) (p.getX() * scale + this.drawerCenter.x + xTranslate
						* scale),
				(int) (p.getY() * scale + this.drawerCenter.y + yTranslate
						* scale));
	}

	public Coord getAbsolutePoint(Point local) {
		return new Coord(
				(int) ((local.x - this.drawerCenter.x) / scale - xTranslate),
				(int) ((local.y - this.drawerCenter.y) / scale - yTranslate));
	}

	public void shift(int dx, int dy) {
		xTranslate += dx / scale;
		yTranslate += dy / scale;
		// drawerCenter = new Point(drawerCenter.x+dx,drawerCenter.y+dy);
	}

	public void drawSelectedStation(Station station, Graphics g) {
		Point center = getLocalPoint(station.getPosition());
		saveGrpahicsParam(g);
		g.setColor(new Color(255, 0, 0, 128));

		g.fillOval((int) (center.x - radius * SelectionRadiusTransform),
				(int) (center.y - radius * SelectionRadiusTransform),
				(int) (radius * SelectionRadiusTransform * 2), (int) (radius
						* SelectionRadiusTransform * 2));
		restoreGraphicsParam(g);
	}

	public void drawSelectedTransition(Transition transition, Graphics g) {
		Point from = getLocalPoint(transition.getFromStation().getPosition());
		Point to = getLocalPoint(transition.getToStation().getPosition());

		saveGrpahicsParam(g);
		((Graphics2D) g).setStroke(selectedTunnelStroke);
		g.setColor(new Color(255, 0, 0, 64));

		g.drawLine(from.x, from.y, to.x, to.y);
		restoreGraphicsParam(g);

	}

	@Override
	public List<Station> getStations(Point point) {
		List<Station> stations = new LinkedList<Station>();
		for (Station station : EditorModel.getInstance().getData()
				.getStations()) {
			if (distance(point, this.getLocalPoint(station.getPosition())) < this.radius) {
				stations.add(station);
			}
		}
		return stations;
	}

	private double distance(Point a, Point b) {
		return java.awt.Point.distance(a.x, a.y, b.x, b.y);
	}

	@Override
	public Rectangle getRedrawRect(Station station) {
		Point center = getLocalPoint(station.getPosition());
		// TODO now station name label is not counting. need to be fixed
		Rectangle rect = new Rectangle((int) (center.x - radius
				* SelectionRadiusTransform), (int) (center.y - radius
				* SelectionRadiusTransform), (int) (radius
				* SelectionRadiusTransform * 2), (int) (radius
				* SelectionRadiusTransform * 2));
		return rect;
	}

	@Override
	public Rectangle getRedrawRect(Transition trans) {
		Point center1 = getLocalPoint(trans.getFromStation().getPosition());
		Point center2 = getLocalPoint(trans.getToStation().getPosition());
		int width = center1.x - center2.x;
		int height = center1.y - center2.y;

		Rectangle rect = new Rectangle(center2.x, center2.y, width, height);
		return rect;
	}

	@Override
	public float getScale() {
		return scale;
	}

	@Override
	public void setScale(float scale) {

		this.scale = scale;

	}

	@Override
	public List<Transition> getTransitions(Point point) {
		List<Transition> list = new LinkedList<Transition>();
		for (PolyStruct ps : polygons) {
			if (ps.polygon.contains(point))
				list.add(ps.tunnel);
		}
		return list;
	}

	@Override
	public Station getLabelsStation(Point point) {
		for (NameStruct ns : names) {
			if (ns.rect.contains(point))
				return ns.station;
		}
		return null;
	}

	@Override
	public void setDrawerCenter(int x, int y) {
		drawerCenter = new Point(x, y);

	}

}
